//
// cbin - compile a linkable object from files and directories
//
// Copyright (C) 2010 Edward Hutchins
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//

#include <unistd.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <bfd.h>

//
// types
//

#define VERSION "1.0"
#define DEFAULT_ROOTNAME "resource_root"
#define DEFAULT_SECTNAME ".res"

#define FAIL(x) \
do { \
	printf x; \
	if (errno) printf( "%s(%d): %s\n", __FILE__, __LINE__, strerror( errno ) ); \
	exit( 1 ); \
} while (0)

template <typename T> void clear( T &x ) { memset( &x, 0, sizeof(x) ); }
template <typename T> void clear( T &x, int count ) { memset( &x, 0, sizeof(x) * count ); }

// resource descriptor which chains all resources together
template <typename T> struct resdesc
{
	T				size;		// size in bytes of the data (not including null-termination)
	T				data;		// offset to the null-terminated data
	T				name;		// offset to the null-terminated source filename if present
	T				next;		// offset to the next sibling descriptor
};

typedef resdesc<u_int32_t> resdesc32;
typedef resdesc<u_int64_t> resdesc64;
typedef union
{
	resdesc32 r32;
	resdesc64 r64;
} resdescunion;

// file or directory descriptor
class object
{
	static object	*_root;		// list of all objects scanned
	static size_t	_allocated;	// global allocation to date
	static int		_count;		// total number of objects
	static int		_align;		// 2^_align alignment for allocations

	object			*_parent;	// parent directory or NULL for root level
	object			*_next;		// sibling

public:
	char			*path;		// full filename from root
	char			*filename;	// name following the last '/'
	size_t			size;		// size of data not including null-termination
	size_t			roff;		// resource descriptor offset
	struct stat		st;			// stat of object
	resdesc64		rd;			// resource descriptor (always 64-bit for now)

	object( object *parent, const char *name, size_t size, const struct stat &st ):
		size(size), st(st), _parent(parent), _next(NULL), roff(0)
	{
		if (!parent)
		{
			path = new char[strlen( name ) + 1];
			strcpy( path, name );
			// link all objects in a LIFO stack to make building the final resdesc list simpler
			setnext( _root );
			_root = this;
		}
		else
		{
			path = new char[strlen( parent->path ) + strlen( name ) + 2];
			sprintf( path, "%s/%s", parent->path, name );
			parent->addchild( this );
		}

		// keep a shortcut to just the filename
		filename = path;
		for (char *p = path; *p; ++p)
			if (*p == '/')
				filename = ++p;

		if (size > 0)
		{
			// waste some space using resdesc64 on 32-bit systems in the interest of simplicity
			roff = allocate( sizeof(rd) );
			rd.size = size;
			rd.data = allocate( size + 1 );
			rd.name = allocate( strlen( path ) + 1 );
			rd.next = 0; // gets linked together before writing this object
			++_count;
		}
	}
	virtual ~object()
	{
		delete [] path;
	}

	static object *root() { return _root; }
	static size_t allocated() { return _allocated; }
	static size_t allocate( size_t size )
	{
		size_t m = (1 << _align) - 1;
		_allocated = ((_allocated + m) & ~m);
		size_t o = _allocated;
		_allocated += size;
		return o;
	}
	static int count() { return _count; }
	static int align() { return _align; }

	void setnext( object *obj ) { _next = obj; }
	object *next() { return _next; }

	virtual object *addchild( object *obj ) { FAIL(( "Attempting to add child to regular file\n" )); }
	virtual object *child() { return NULL; }
};

object *object::_root = NULL;
size_t object::_allocated = 0;
int object::_count = 0;
// default to 16-byte alignment for each object (get from bfd arch?)
int object::_align = 4;

// extended descriptor for directories
class dir: public object
{
	object *_child;

public:
	dir( object *parent, const char *name, size_t size, const struct stat &st ): object( parent, name, size, st ), _child(NULL) {}
	// link all child objects in a LIFO stack to make building the final resdesc list simpler
	virtual object *addchild( object *obj ) { obj->setnext( _child ); _child = obj; }
	virtual object *child() { return _child; }
};

//
// globals
//

static const char *namespacename = NULL;
static int indentsize = 4;
static bool verbose = false;

//
// mangle_namespace - g++-specific namespace mangler
//

char *mangle_namespace( const char *name )
{
	static char buff[PATH_MAX];
	char *pd = buff;
	const char *ps = name;
	bool mangled = false;

	for (const char *p = ps; *p; ++p)
	{
		if ((p[0] == ':') && (p[1] == ':'))
		{
			if (!mangled)
			{
				pd += sprintf( pd, "_ZN" );
				mangled = true;
			}
			pd += sprintf( pd, "%d", int(p - ps) );
			while (ps < p) *pd++ = *ps++;
			while (*p == ':') ++p;
			// undo the for increment but leave ps pointing at the new word
			ps = p--;
		}
	}
	if (!*ps) FAIL(( "Illegal name \"%s\"\n", name ));
	if (mangled) pd += sprintf( pd, "%d%sE", strlen( ps ), ps );
	else strcpy( pd, ps );

	return buff;
}

//
// make_symbol_name - convert a filename into a legal C/C++ symbol name
//

char *make_symbol_name( const char *prefix, const char *name, const char *suffix )
{
	static char buff[PATH_MAX];
	char *pd = buff;
	char *pext = NULL;

	if (prefix) pd += sprintf( pd, "%s::", prefix );
	for (const char *ps = name; *ps; ++ps)
	{
		if (*ps == '.') pext = pd;
		if (isalnum( *ps )) *pd++ = *ps;
		else if (prefix && (*ps == '/'))
		{
			// each directory becomes a new namespace
			*pd++ = ':'; *pd++ = ':';
		}
		else *pd++ = '_';
	}
	*pd = '\0';
	if (suffix)
	{
		if (pext) strcpy( pext, suffix );
		else strcat( pd, suffix );
	}

	return buff;
}

//
// add_file - add a file to be compiled into the object
//

object *add_file( const char *name, object *parent = NULL )
{
	if (name[0] == '/') FAIL(( "Absolute path \"%s\" not allowed\n", name ));

	char path[PATH_MAX];
	char *pd = path;

	if (parent)
	{
		pd += sprintf( pd, "%s", parent->path );
		if ((pd > path) && (pd[-1] != '/')) *pd++ = '/';
	}

	struct stat st;

	// check for path characters in the name and insert the appropriate dummy dirs
	char *pdirname = pd;
	const char *ps = name;
	while (*ps)
	{
		if (*ps == '/')
		{
			++ps;
			*pd = '\0';
			if (strcmp( "..", pdirname ) == 0)
				FAIL(( "Relative path \"%s\" not allowed\n", name ));

			if (verbose) printf( "adding placeholder dir \"%s\"...\n", path );
			if (stat( path, &st ) != 0)
				FAIL(( "Failed to stat \"%s\"\n", path ));
			object *dummydir = new dir( parent, pdirname, 0, st );
			add_file( ps, dummydir );
			return dummydir;
		}
		*pd++ = *ps++;
	}
	*pd = '\0';

	if (stat( path, &st ) != 0)
		FAIL(( "Failed to stat \"%s\"\n", path ));

	if (!S_ISREG( st.st_mode ) && !S_ISDIR( st.st_mode ))
		FAIL(( "\"%s\" is not a file or directory\n", path ));

	if (st.st_size < 1)
		return NULL;

	if (!S_ISDIR( st.st_mode ))
	{
		if (verbose) printf( "adding file \"%s\"...\n", path );
		return new object( parent, name, st.st_size, st );
	}

	DIR *d = opendir( path );
	if (!d) FAIL(( "Failed to open directory \"%s\"\n", path ));

	if (verbose) printf( "adding dir \"%s\"...\n", path );
	object *newdir = new dir( parent, name, 0, st );

	while (struct dirent *de = readdir( d ))
	{
		// skip all dotfiles...
		if (de->d_name[0] == '.') continue;
		add_file( de->d_name, newdir );
	}

	closedir( d );
	
	return newdir;
}

//
// usage - show usage help text
//

void usage( int code, const char *prog )
{
	const char **tlist = bfd_target_list();
	const char **alist = bfd_arch_list();

	printf( "usage: %s <opts> <file> [<file>...]\n", prog );
	printf( "<opts> can be one of:\n"
		"-o <outfile>:\tset output filename (default guessed from first input)\n"
		"-t <target>:\tselect object target\n"
		"-a <arch>:\tselect object architecture\n"
		"-n <name>:\tput all resources into the C++ namespace <name>\n"
		"-i <indent>:\tset indent spaces for prototype namespace nesting\n"
		"-r <root>:\tset the resource root descriptor name to <root>\n"
		"-s <sect>:\tset the resource section name to <sect>\n"
		"-p:\t\tgenerate C/C++ prototypes instead of object files\n"
		"-P <header>:\tgenerate both objects in <outfile> and prototypes in <header>\n"
		"-d <pfx>:\tgenerate \"#define <pfx>name\" along with prototypes\n"
		"-v:\t\tverbose\n\n"
		"The default <outfile> is the name of the first <file> with the suffix \".res\"\n"
		"substituted. <target> and <arch> default to sane settings for the current system.\n"
		"The default <root> is " DEFAULT_ROOTNAME ". The default <sect> is \"" DEFAULT_SECTNAME "\"\n"
		"\n"
	);

	printf( "<target> can be one of:" );
	for (int i = 0; tlist[i]; ++i) printf( " %s", tlist[i] );
	printf( "\n" );

	printf( "<arch> can be one of:" );
	for (int i = 0; alist[i]; ++i) printf( " %s", alist[i] );
	printf( "\n" );

	exit( code );
}

//
// parse_name - parse a name from the arguments
//

char *parse_name( int argc, char *argv[], int &i )
{
	if (argv[i][2]) return &argv[i][2];
	if (++i < argc) return argv[i];
	usage( 1, argv[0] );
}

//
// print_arch - display detailed information about an architecture
//

void print_arch( const char *name )
{
	const bfd_arch_info_type *arch = bfd_scan_arch( name );
	if (!arch)
	{
		printf( "unknown architecture \"%s\"\n", name );
		return;
	}
	printf( "archinfo %s: wordbits = %d, pointerbits = %d, bytebits = %d, archid = %d, machid = %ld, (%s) \"%s\", align = 2^%d%s\n", name,
		arch->bits_per_word, arch->bits_per_address, arch->bits_per_byte,
		arch->arch, arch->mach, arch->arch_name, arch->printable_name,
		arch->section_align_power, (arch->the_default ? " (default)" : "") );
}

//
// print_objects - display the object tree
//

void print_objects( object *o )
{
	while (o)
	{
		if (o->child()) print_objects( o->child() );
		else printf( "\"%s\" @0x%lx [%ld]\n", o->path, o->rd.data, o->rd.size );
		o = o->next();
	}
}

//
// print_indent - print some white space
//

inline void print_indent( FILE *f, int indent )
{
	for (int i = 0; i < indent * indentsize; ++i) putc( ' ', f );
}

//
// print_resdesc_decl - output a struct to enable navigating the resources
//

void print_resdesc_decl( FILE *f, int indent )
{
	print_indent( f, indent );
	fprintf( f,	"typedef struct resdesc {\n" );
	print_indent( f, indent + 1 );
	fprintf( f,	"size_t          size; // size of data in bytes\n" );
	print_indent( f, indent + 1 );
	fprintf( f,	"void           *data; // raw bytes from file\n" );
	print_indent( f, indent + 1 );
	fprintf( f,	"char           *name; // null-terminated filename\n" );
	print_indent( f, indent + 1 );
	fprintf( f,	"struct resdesc *next; // next resource, or null\n" );
	print_indent( f, indent );
	fprintf( f,	"} resdesc;\n\n" );
}

//
// print_prototypes - generate prototypes with a namespace for each directory
//

void print_prototypes( FILE *f, object *o, int indent )
{
	while (o)
	{
		if (o->child())
		{
			print_indent( f, indent );
			fprintf( f, "namespace %s {\n", make_symbol_name( NULL, o->filename, NULL ) );
			print_prototypes( f, o->child(), indent + 1 );
			print_indent( f, indent );
			fprintf( f, "}\n" );
		}
		else
		{
			print_indent( f, indent );
			fprintf( f, "extern char %s[];\n", make_symbol_name( NULL, o->filename, NULL ) );
		}
		o = o->next();
	}
}

//
// make_object_table - flatten file objects into a table for easier processing
//

void make_object_table( object *o, int &next, object *objtab[] )
{
	while (o)
	{
		if (o->child())
		{
			make_object_table( o->child(), next, objtab );
		}
		else
		{
			if (next >= object::count()) FAIL(( "Object count incorrect!\n" ));
			objtab[next++] = o;
		}
		o = o->next();
	}
}

//
// main - parse the command-line arguments and generate the object
//

int main( int argc, char *argv[] )
{
	bfd_init();
	const char **tlist = bfd_target_list();
	const char **alist = bfd_arch_list();
	const char *targetname = tlist[0];
	const char *archname = alist[0];
	unsigned long mach = 0;
	const char *outfile = NULL;
	const char *protofile = NULL;
	const char *rootname = DEFAULT_ROOTNAME;
	const char *sectname = DEFAULT_SECTNAME;
	const char *havedef = NULL;
	bool prototype = false;

	// for some reason x86_64 systems default to target elf64-x86-64 but arch i386?
	int default_bits_per_address = (sizeof(void *) == 8) ? 64 : 32;
	for (int i = 0; alist[i]; ++i)
	{
		const bfd_arch_info_type *arch = bfd_scan_arch( alist[i] );
		if (arch->bits_per_address == default_bits_per_address)
		{
			// BFD BUG? arch->arch_name returns a string which bfd_scan_arch returns a different pointer for!
			// arch->printable_name maps back to this bfd_arch_info_type
			archname = arch->printable_name;
			mach = arch->mach;
			break;
		}
	}

	for (int i = 1; i < argc; ++i) if (argv[i][0] == '-') switch (argv[i][1])
	{
	case 'o': outfile = parse_name( argc, argv, i ); break;
	case 't': targetname = parse_name( argc, argv, i ); break;
	case 'a': archname = parse_name( argc, argv, i ); break;
	case 'n': namespacename = parse_name( argc, argv, i ); break;
	case 'i': indentsize = atoi( parse_name( argc, argv, i ) ); break;
	case 'r': rootname = parse_name( argc, argv, i ); break;
	case 's': sectname = parse_name( argc, argv, i ); break;
	case 'd': havedef = parse_name( argc, argv, i ); break;
	case 'p': prototype = true; break;
	case 'P': protofile = parse_name( argc, argv, i ); break;
	case 'v': verbose = true; break;
	case 'h': default: usage( (argv[i][1] != 'h'), argv[0] );
	}
	else
	{
		if (!outfile) outfile = strdup( make_symbol_name( NULL, argv[i], (prototype ? ".h" : ".res") ) );
		add_file( argv[i] );
	}

	if (!object::root()) usage( 1, argv[0] );
	if (!outfile) FAIL(( "No output file name specified\n" ));

	const bfd_arch_info_type *arch = bfd_scan_arch( archname );
	//const bfd_arch_info_type *arch = bfd_lookup_arch( bfd_arch_i386, bfd_mach_x86_64 );
	if ((arch->bits_per_address != 32) && (arch->bits_per_address != 64))
		FAIL(( "Only 32 and 64-bit architectures currently supported!\n" ));

	if (verbose)
	{
		printf( "writing %d objects to %s...\n", object::count(), outfile );
		print_objects( object::root() );
		printf( "%ld bytes total\n", object::allocated() );
	}

	// build a flat table of object pointers
	object **objtab = new object*[object::count()];
	int nextobj = 0;
	make_object_table( object::root(), nextobj, objtab );
	if (nextobj != object::count()) FAIL(( "Object count mismatch!\n" ));

	// build the prototypes
	if (prototype || protofile)
	{
		const char *headername = protofile ? protofile : outfile;
		if (verbose) printf( "generating %s\n", headername );

		FILE *f = fopen( headername, "w" );

		char *lockername = strdup( make_symbol_name( NULL, headername, NULL ) );
		fprintf( f, "#if !defined(_%s)\n#define _%s\n\n", lockername, lockername );
		fprintf( f, "//\n// %s - do not edit, resource header generated with cbin v" VERSION "\n//\n\n", headername );

		if (havedef)
		{
			for (int i = 0; i < object::count(); ++i)
				fprintf( f, "#define %s%s\n", havedef, make_symbol_name( NULL, objtab[i]->path, NULL ) );
			fprintf( f, "#define %s%s\n\n", havedef, make_symbol_name( NULL, rootname, NULL ) );
		}

		if (namespacename)
		{
			fprintf( f, "namespace %s {\n", namespacename );
			print_resdesc_decl( f, 1 );
			print_indent( f, 1 );
			fprintf( f, "extern resdesc %s;\n\n", make_symbol_name( NULL, rootname, NULL ) );
			print_prototypes( f, object::root(), 1 );
			fprintf( f, "} // namespace %s\n\n", namespacename );
		}
		else // flat C-style symbols
		{
			print_resdesc_decl( f, 0 );
			fprintf( f, "extern resdesc %s;\n\n", make_symbol_name( NULL, rootname, NULL ) );
			for (int i = 0; i < object::count(); ++i)
				fprintf( f, "extern char %s[];\n", make_symbol_name( NULL, objtab[i]->path, NULL ) );
		}

		fprintf( f, "#endif // !defined(_%s)\n", lockername );

		fclose( f );
		
		// we're done if the user specified -p
		if (prototype) return 0;
	}

	// build the resource object file
	if (verbose) printf( "generating %s for target %s arch %s\n", outfile, targetname, archname );
	bfd *abfd = bfd_openw( outfile, targetname );
	bfd_set_format( abfd, bfd_object );
	if (arch) bfd_set_arch_info( abfd, arch );

	asection *sec = bfd_make_section_with_flags( abfd, sectname,
		SEC_LOAD | SEC_DATA | SEC_ALLOC | SEC_HAS_CONTENTS | SEC_RELOC );
	bfd_set_section_size( abfd, sec, object::allocated() );
	sec->alignment_power = object::align();

	reloc_howto_type *howto = bfd_reloc_type_lookup( abfd, (arch->bits_per_address == 32) ? BFD_RELOC_32 : BFD_RELOC_64 );
	if (!howto) FAIL(( "Target %s incompatible with arch %s\n", targetname, archname ));
	if (verbose) printf( "using reloc %s [%d bytes]\n", howto->name, bfd_get_reloc_size( howto ) );

	// add symbols for each object, plus a special symbol pointing at the first resdesc
	int symcount = object::count() + 1;
	// apparently the array of symbols needs a NULL entry at the end
	asymbol **symtab = new asymbol*[symcount + 1];
	symtab[symcount] = NULL;

	size_t last_offset = 0;
	for (int i = 0; i < object::count(); ++i)
	{
		symtab[i] = bfd_make_empty_symbol( abfd );
		symtab[i]->name = strdup( mangle_namespace( make_symbol_name( namespacename, objtab[i]->path, NULL ) ) );
		symtab[i]->section = sec;
		symtab[i]->flags = BSF_GLOBAL | BSF_OBJECT;
		symtab[i]->value = objtab[i]->rd.data;
		// link the resdesc structs together, the head of the list is actually
		// objtab[object::count() - 1], which was the first object added. The
		// first object added very helpfully has its resdesc at offset 0.
		objtab[i]->rd.next = last_offset;
		last_offset = objtab[i]->roff;
	}

	int rootsym = symcount - 1;
	symtab[rootsym] = bfd_make_empty_symbol( abfd );
	symtab[rootsym]->name = strdup( mangle_namespace( make_symbol_name( namespacename, rootname, NULL ) ) );
	symtab[rootsym]->section = sec;
	symtab[rootsym]->flags = BSF_GLOBAL | BSF_OBJECT;
	symtab[rootsym]->value = last_offset;

	// adding the symbol table apparently has to happen before we add any contents...
	bfd_set_symtab( abfd, symtab, symcount );

	// add the relocation records (3 per object data, name and next pointers, not counting the final NULL next)
	int relcount = (3 * object::count()) - 1;
	arelent **reltable = new arelent*[relcount + 1];
	reltable[relcount] = NULL;
	int nextrel = 0;

	int resdesc_data_offset, resdesc_name_offset, resdesc_next_offset;
	switch (arch->bits_per_address)
	{
	case 32:
		resdesc_data_offset = offsetof(resdesc32,data);
		resdesc_name_offset = offsetof(resdesc32,name);
		resdesc_next_offset = offsetof(resdesc32,next);
		break;
	case 64:
		resdesc_data_offset = offsetof(resdesc64,data);
		resdesc_name_offset = offsetof(resdesc64,name);
		resdesc_next_offset = offsetof(resdesc64,next);
		break;
	}

	for (int i = 0; i < object::count(); ++i)
	{
		// fixup the data pointer (just use the data symbol directly since the addend is 0)
		arelent *re = new arelent();
		re->sym_ptr_ptr = &symtab[i];
		re->address = objtab[i]->roff + resdesc_data_offset;
		re->addend = 0;
		re->howto = howto;
		reltable[nextrel++] = re;

		// fixup the name (relative to the root symbol)
		re = new arelent();
		re->sym_ptr_ptr = &symtab[rootsym];
		re->address = objtab[i]->roff + resdesc_name_offset;
		re->addend = objtab[i]->rd.name;
		re->howto = howto;
		reltable[nextrel++] = re;

		// fixup non-null next pointers (relative to the root symbol)
		if (objtab[i]->rd.next)
		{
			re = new arelent();
			re->sym_ptr_ptr = &symtab[rootsym];
			re->address = objtab[i]->roff + resdesc_next_offset;
			re->addend = objtab[i]->rd.next;
			re->howto = howto;
			reltable[nextrel++] = re;
		}
	}
	if (nextrel != relcount) FAIL(( "Internal error building relocation table!\n" ));

	// add the relocations
	bfd_set_reloc( abfd, sec, reltable, relcount );

	// write out the objects
	for (int i = 0; i < object::count(); ++i)
	{
		// convert the resource descriptor to the correct target format
		// note that the data, name and next pointers are handled with relocations
		resdescunion rd;
		clear( rd );
		switch (arch->bits_per_address)
		{
		case 32:
			bfd_put_32( abfd, objtab[i]->rd.size, &rd.r32.size );
			break;
		case 64:
			bfd_put_64( abfd, objtab[i]->rd.size, &rd.r64.size );
			break;
		}

		if (!bfd_set_section_contents( abfd, sec, &rd, objtab[i]->roff, sizeof(rd) ))
		{
			bfd_perror( "BFD error:" );
			FAIL(( "Failed to add \"%s\" resource descriptor\n", objtab[i]->path ));
		}

		if (!bfd_set_section_contents( abfd, sec, objtab[i]->path, objtab[i]->rd.name, strlen( objtab[i]->path ) + 1 ))
		{
			bfd_perror( "BFD error:" );
			FAIL(( "Failed to add \"%s\" resource name\n", objtab[i]->path ));
		}

		if (verbose) printf( "adding \"%s\"...\n", objtab[i]->path );
		int fd = open( objtab[i]->path, O_RDONLY );
		if (fd == -1) FAIL(( "Failed to open \"%s\"\n", objtab[i]->path ));
		void *mapping = mmap( NULL, objtab[i]->rd.size, PROT_READ, MAP_PRIVATE, fd, 0 );
		if (!mapping) FAIL(( "Failed to map \"%s\"\n", objtab[i]->path ));

		if (!bfd_set_section_contents( abfd, sec, mapping, objtab[i]->rd.data, objtab[i]->rd.size ))
		{
			bfd_perror( "BFD error:" );
			FAIL(( "Failed to add \"%s\" data\n", objtab[i]->path ));
		}

		munmap( mapping, objtab[i]->rd.size );
		close( fd );

		// NULL-terminate the data to make grabbing embedded text files easier
		static char endstr[] = { 0 };
		if (!bfd_set_section_contents( abfd, sec, endstr, objtab[i]->rd.data + objtab[i]->rd.size, 1 ))
		{
			bfd_perror( "BFD error:" );
			FAIL(( "Failed to add \"%s\" data terminator\n", objtab[i]->path ));
		}
	}

	bfd_close( abfd );

	return 0;
}
